﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedMember.Local

/* PdfImage.FaxEncode.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#define USE_GOTO

#region Using directives

using System;
using System.Diagnostics;

using AM;

#endregion

#nullable enable

namespace PdfSharpCore.Pdf.Advanced;

partial class PdfImage
{
    internal static readonly uint[] WhiteTerminatingCodes =
    {
        0x35, 8, //00110101 // 0
        0x07, 6, //000111
        0x07, 4, //0111
        0x08, 4, //1000
        0x0b, 4, //1011
        0x0c, 4, //1100
        0x0e, 4, //1110
        0x0f, 4, //1111
        0x13, 5, //10011
        0x14, 5, //10100
        0x07, 5, //00111    // 10
        0x08, 5, //01000
        0x08, 6, //001000
        0x03, 6, //000011
        0x34, 6, //110100
        0x35, 6, //110101
        0x2a, 6, //101010   // 16
        0x2b, 6, //101011
        0x27, 7, //0100111
        0x0c, 7, //0001100
        0x08, 7, //0001000  // 20
        0x17, 7, //0010111
        0x03, 7, //0000011
        0x04, 7, //0000100
        0x28, 7, //0101000
        0x2b, 7, //0101011
        0x13, 7, //0010011
        0x24, 7, //0100100
        0x18, 7, //0011000
        0x02, 8, //00000010
        0x03, 8, //00000011 // 30
        0x1a, 8, //00011010
        0x1b, 8, //00011011 // 32
        0x12, 8, //00010010
        0x13, 8, //00010011
        0x14, 8, //00010100
        0x15, 8, //00010101
        0x16, 8, //00010110
        0x17, 8, //00010111
        0x28, 8, //00101000
        0x29, 8, //00101001 // 40
        0x2a, 8, //00101010
        0x2b, 8, //00101011
        0x2c, 8, //00101100
        0x2d, 8, //00101101
        0x04, 8, //00000100
        0x05, 8, //00000101
        0x0a, 8, //00001010
        0x0b, 8, //00001011 // 48
        0x52, 8, //01010010
        0x53, 8, //01010011 // 50
        0x54, 8, //01010100
        0x55, 8, //01010101
        0x24, 8, //00100100
        0x25, 8, //00100101
        0x58, 8, //01011000
        0x59, 8, //01011001
        0x5a, 8, //01011010
        0x5b, 8, //01011011
        0x4a, 8, //01001010
        0x4b, 8, //01001011 // 60
        0x32, 8, //00110010
        0x33, 8, //00110011
        0x34, 8, //00110100 // 63
    };

    internal static readonly uint[] BlackTerminatingCodes =
    {
        0x37, 10, //0000110111   // 0
        0x02, 3, //010
        0x03, 2, //11
        0x02, 2, //10
        0x03, 3, //011
        0x03, 4, //0011
        0x02, 4, //0010
        0x03, 5, //00011
        0x05, 6, //000101
        0x04, 6, //000100
        0x04, 7, //0000100
        0x05, 7, //0000101
        0x07, 7, //0000111
        0x04, 8, //00000100
        0x07, 8, //00000111
        0x18, 9, //000011000
        0x17, 10, //0000010111   // 16
        0x18, 10, //0000011000
        0x08, 10, //0000001000
        0x67, 11, //00001100111
        0x68, 11, //00001101000
        0x6c, 11, //00001101100
        0x37, 11, //00000110111
        0x28, 11, //00000101000
        0x17, 11, //00000010111
        0x18, 11, //00000011000
        0xca, 12, //000011001010
        0xcb, 12, //000011001011
        0xcc, 12, //000011001100
        0xcd, 12, //000011001101
        0x68, 12, //000001101000 // 30
        0x69, 12, //000001101001
        0x6a, 12, //000001101010 // 32
        0x6b, 12, //000001101011
        0xd2, 12, //000011010010
        0xd3, 12, //000011010011
        0xd4, 12, //000011010100
        0xd5, 12, //000011010101
        0xd6, 12, //000011010110
        0xd7, 12, //000011010111
        0x6c, 12, //000001101100
        0x6d, 12, //000001101101
        0xda, 12, //000011011010
        0xdb, 12, //000011011011
        0x54, 12, //000001010100
        0x55, 12, //000001010101
        0x56, 12, //000001010110
        0x57, 12, //000001010111
        0x64, 12, //000001100100 // 48
        0x65, 12, //000001100101
        0x52, 12, //000001010010
        0x53, 12, //000001010011
        0x24, 12, //000000100100
        0x37, 12, //000000110111
        0x38, 12, //000000111000
        0x27, 12, //000000100111
        0x28, 12, //000000101000
        0x58, 12, //000001011000
        0x59, 12, //000001011001
        0x2b, 12, //000000101011
        0x2c, 12, //000000101100
        0x5a, 12, //000001011010
        0x66, 12, //000001100110
        0x67, 12, //000001100111 // 63
    };

    internal static readonly uint[] WhiteMakeUpCodes =
    {
        0x1b, 5, //11011 64          // 0
        0x12, 5, //10010 128
        0x17, 6, //010111 192
        0x37, 7, //0110111 256
        0x36, 8, //00110110 320
        0x37, 8, //00110111 384
        0x64, 8, //01100100 448
        0x65, 8, //01100101 512
        0x68, 8, //01101000 576
        0x67, 8, //01100111 640
        0xcc, 9, //011001100 704     // 10
        0xcd, 9, //011001101 768
        0xd2, 9, //011010010 832
        0xd3, 9, //011010011 896
        0xd4, 9, //011010100 960
        0xd5, 9, //011010101 1024
        0xd6, 9, //011010110 1088    // 16
        0xd7, 9, //011010111 1152
        0xd8, 9, //011011000 1216
        0xd9, 9, //011011001 1280
        0xda, 9, //011011010 1344
        0xdb, 9, //011011011 1408
        0x98, 9, //010011000 1472
        0x99, 9, //010011001 1536
        0x9a, 9, //010011010 1600
        0x18, 6, //011000    1664
        0x9b, 9, //010011011 1728
        // Common codes for white and black:
        0x08, 11, //00000001000 1792
        0x0c, 11, //00000001100 1856
        0x0d, 11, //00000001101 1920
        0x12, 12, //000000010010 1984
        0x13, 12, //000000010011 2048
        0x14, 12, //000000010100 2112 // 32
        0x15, 12, //000000010101 2176
        0x16, 12, //000000010110 2240
        0x17, 12, //000000010111 2304
        0x1c, 12, //000000011100 2368
        0x1d, 12, //000000011101 2432
        0x1e, 12, //000000011110 2496
        0x1f, 12, //000000011111 2560
        0x01, 12, //000000000001 EOL  // 40
    };

    internal static readonly uint[] BlackMakeUpCodes =
    {
        0x0f, 10, //0000001111    64   // 0
        0xc8, 12, //000011001000  128
        0xc9, 12, //000011001001  192
        0x5b, 12, //000001011011  256
        0x33, 12, //000000110011  320
        0x34, 12, //000000110100  384
        0x35, 12, //000000110101  448
        0x6c, 13, //0000001101100 512
        0x6d, 13, //0000001101101 576
        0x4a, 13, //0000001001010 640
        0x4b, 13, //0000001001011 704
        0x4c, 13, //0000001001100 768
        0x4d, 13, //0000001001101 832
        0x72, 13, //0000001110010 896
        0x73, 13, //0000001110011 960
        0x74, 13, //0000001110100 1024
        0x75, 13, //0000001110101 1088 // 16
        0x76, 13, //0000001110110 1152
        0x77, 13, //0000001110111 1216
        0x52, 13, //0000001010010 1280
        0x53, 13, //0000001010011 1344
        0x54, 13, //0000001010100 1408
        0x55, 13, //0000001010101 1472
        0x5a, 13, //0000001011010 1536
        0x5b, 13, //0000001011011 1600
        0x64, 13, //0000001100100 1664
        0x65, 13, //0000001100101 1728
        // Common codes for white and black:
        0x08, 11, //00000001000 1792
        0x0c, 11, //00000001100 1856
        0x0d, 11, //00000001101 1920
        0x12, 12, //000000010010 1984
        0x13, 12, //000000010011 2048
        0x14, 12, //000000010100 2112  // 32
        0x15, 12, //000000010101 2176
        0x16, 12, //000000010110 2240
        0x17, 12, //000000010111 2304
        0x1c, 12, //000000011100 2368
        0x1d, 12, //000000011101 2432
        0x1e, 12, //000000011110 2496
        0x1f, 12, //000000011111 2560
        0x01, 12, //000000000001 EOL   // 40
    };

    internal static readonly uint[] HorizontalCodes = { 0x1, 3 }; /* 001 */
    internal static readonly uint[] PassCodes = { 0x1, 4, }; /* 0001 */

    internal static readonly uint[] VerticalCodes =
    {
        0x03, 7, /* 0000 011 */
        0x03, 6, /* 0000 11 */
        0x03, 3, /* 011 */
        0x1, 1, /* 1 */
        0x2, 3, /* 010 */
        0x02, 6, /* 0000 10 */
        0x02, 7, /* 0000 010 */
    };

    static readonly uint[] _zeroRuns =
    {
        8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0f */
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1f */
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2f */
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3f */
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4f */
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5f */
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6f */
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7f */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xf0 - 0xff */
    };

    static readonly uint[] _oneRuns =
    {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0f */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6f */
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 - 0x8f */
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 - 0x9f */
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xa0 - 0xaf */
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xb0 - 0xbf */
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xc0 - 0xcf */
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xd0 - 0xdf */
        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xe0 - 0xef */
        4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, /* 0xf0 - 0xff */
    };

    /// <summary>
    /// Counts the consecutive one bits in an image line.
    /// </summary>
    /// <param name="reader">The reader.</param>
    /// <param name="bitsLeft">The bits left.</param>
    private static uint CountOneBits (BitReader reader, uint bitsLeft)
    {
        uint found = 0;
        for (;;)
        {
            int @byte = reader.PeekByte (out var bits);
            var hits = _oneRuns[@byte];
            if (hits < bits)
            {
                if (hits > 0)
                    reader.SkipBits (hits);
                found += hits;
                return found >= bitsLeft ? bitsLeft : found;
            }

            found += bits;
            if (found >= bitsLeft)
                return bitsLeft;
            reader.NextByte();
        }
    }

    /// <summary>
    /// Counts the consecutive zero bits in an image line.
    /// </summary>
    /// <param name="reader">The reader.</param>
    /// <param name="bitsLeft">The bits left.</param>
    private static uint CountZeroBits (BitReader reader, uint bitsLeft)
    {
        uint found = 0;
        for (;;)
        {
            int @byte = reader.PeekByte (out var bits);
            var hits = _zeroRuns[@byte];
            if (hits < bits)
            {
                if (hits > 0)
                    reader.SkipBits (hits);
                found += hits;
                return found >= bitsLeft ? bitsLeft : found;
            }

            found += bits;
            if (found >= bitsLeft)
                return bitsLeft;
            reader.NextByte();
        }
    }

    /// <summary>
    /// Returns the offset of the next bit in the range
    /// [bitStart..bitEnd] that is different from the
    /// specified color.  The end, bitEnd, is returned
    /// if no such bit exists.
    /// </summary>
    /// <param name="reader">The reader.</param>
    /// <param name="bitStart">The offset of the start bit.</param>
    /// <param name="bitEnd">The offset of the end bit.</param>
    /// <param name="searchOne">If set to <c>true</c> searches "one" (i. e. white), otherwise searches black.</param>
    /// <returns>The offset of the first non-matching bit.</returns>
    private static uint FindDifference (BitReader reader, uint bitStart, uint bitEnd, bool searchOne)
    {
        // Translated from LibTiff
        reader.SetPosition (bitStart);
        return bitStart +
               (searchOne ? CountOneBits (reader, bitEnd - bitStart) : CountZeroBits (reader, bitEnd - bitStart));
    }

    /// <summary>
    /// Returns the offset of the next bit in the range
    /// [bitStart..bitEnd] that is different from the
    /// specified color.  The end, bitEnd, is returned
    /// if no such bit exists.
    /// Like FindDifference, but also check the
    /// starting bit against the end in case start > end.
    /// </summary>
    /// <param name="reader">The reader.</param>
    /// <param name="bitStart">The offset of the start bit.</param>
    /// <param name="bitEnd">The offset of the end bit.</param>
    /// <param name="searchOne">If set to <c>true</c> searches "one" (i. e. white), otherwise searches black.</param>
    /// <returns>The offset of the first non-matching bit.</returns>
    private static uint FindDifferenceWithCheck (BitReader reader, uint bitStart, uint bitEnd, bool searchOne)
    {
        // Translated from LibTiff
        return bitStart < bitEnd ? FindDifference (reader, bitStart, bitEnd, searchOne) : bitEnd;
    }

    /// <summary>
    /// 2d-encode a row of pixels. Consult the CCITT documentation for the algorithm.
    /// </summary>
    /// <param name="writer">The writer.</param>
    /// <param name="bytesFileOffset">Offset of image data in bitmap file.</param>
    /// <param name="imageBits">The bitmap file.</param>
    /// <param name="currentRow">Index of the current row.</param>
    /// <param name="referenceRow">Index of the reference row (0xffffffff if there is none).</param>
    /// <param name="width">The width of the image.</param>
    /// <param name="height">The height of the image.</param>
    /// <param name="bytesPerLineBmp">The bytes per line in the bitmap file.</param>
    static void FaxEncode2DRow (BitWriter writer, uint bytesFileOffset, byte[] imageBits, uint currentRow,
        uint referenceRow, uint width, uint height, uint bytesPerLineBmp)
    {
        // Translated from LibTiff
        var bytesOffsetRead = bytesFileOffset + (height - 1 - currentRow) * bytesPerLineBmp;
        var reader = new BitReader (imageBits, bytesOffsetRead, width);
        BitReader readerReference;
        if (referenceRow != 0xffffffff)
        {
            var bytesOffsetReadReference = bytesFileOffset + (height - 1 - referenceRow) * bytesPerLineBmp;
            readerReference = new BitReader (imageBits, bytesOffsetReadReference, width);
        }
        else
        {
            var tmpImageBits = new byte[bytesPerLineBmp];
            for (var i = 0; i < bytesPerLineBmp; ++i)
                tmpImageBits[i] = 255;
            readerReference = new BitReader (tmpImageBits, 0, width);
        }

        uint a0 = 0;
        var a1 = !reader.GetBit (0) ? 0 : FindDifference (reader, 0, width, true);
        var b1 = !readerReference.GetBit (0) ? 0 : FindDifference (readerReference, 0, width, true);

        // ReSharper disable TooWideLocalVariableScope
        uint a2, b2;

        // ReSharper restore TooWideLocalVariableScope

        for (;;)
        {
            b2 = FindDifferenceWithCheck (readerReference, b1, width, readerReference.GetBit (b1));
            if (b2 >= a1)
            {
                var d = (int)b1 - (int)a1;
                if (!(d is >= -3 and <= 3))
                {
                    /* horizontal mode */
                    a2 = FindDifferenceWithCheck (reader, a1, width, reader.GetBit (a1));
                    writer.WriteTableLine (HorizontalCodes, 0);

                    if (a0 + a1 == 0 || reader.GetBit (a0))
                    {
                        WriteSample (writer, a1 - a0, true);
                        WriteSample (writer, a2 - a1, false);
                    }
                    else
                    {
                        WriteSample (writer, a1 - a0, false);
                        WriteSample (writer, a2 - a1, true);
                    }

                    a0 = a2;
                }
                else
                {
                    /* vertical mode */
                    writer.WriteTableLine (VerticalCodes, (uint)(d + 3));
                    a0 = a1;
                }
            }
            else
            {
                /* pass mode */
                writer.WriteTableLine (PassCodes, 0);
                a0 = b2;
            }

            if (a0 >= width)
                break;
            var bitA0 = reader.GetBit (a0);
            a1 = FindDifference (reader, a0, width, bitA0 /*reader.GetBit(a0)*/);
            b1 = FindDifference (readerReference, a0, width, !bitA0 /*reader.GetBit(a0)*/);
            b1 = FindDifferenceWithCheck (readerReference, b1, width, bitA0 /*reader.GetBit(a0)*/);
        }
    }

    /// <summary>
    /// Encodes a bitonal bitmap using 1D CCITT fax encoding.
    /// </summary>
    /// <param name="imageData">Space reserved for the fax encoded bitmap. An exception will be thrown if this buffer is too small.</param>
    /// <param name="imageBits">The bitmap to be encoded.</param>
    /// <param name="bytesFileOffset">Offset of image data in bitmap file.</param>
    /// <param name="width">The width of the image.</param>
    /// <param name="height">The height of the image.</param>
    /// <returns>The size of the fax encoded image (0 on failure).</returns>
    private static int DoFaxEncoding
        (
            ref byte[] imageData,
            byte[] imageBits,
            uint bytesFileOffset,
            uint width,
            uint height
        )
    {
        try
        {
            var bytesPerLineBmp = (width + 31) / 32 * 4;
            var writer = new BitWriter (ref imageData);
            for (uint y = 0; y < height; ++y)
            {
                var bytesOffsetRead = bytesFileOffset + (height - 1 - y) * bytesPerLineBmp;
                var reader = new BitReader (imageBits, bytesOffsetRead, width);
                for (uint bitsRead = 0; bitsRead < width;)
                {
                    var white = CountOneBits (reader, width - bitsRead);
                    WriteSample (writer, white, true);
                    bitsRead += white;
                    if (bitsRead < width)
                    {
                        var black = CountZeroBits (reader, width - bitsRead);
                        WriteSample (writer, black, false);
                        bitsRead += black;
                    }
                }
            }

            writer.FlushBuffer();
            return writer.BytesWritten();
        }
        catch (Exception /*ex*/)
        {
            //ex.GetType();
            return 0;
        }
    }

    /// <summary>
    /// Encodes a bitonal bitmap using 2D group 4 CCITT fax encoding.
    /// </summary>
    /// <param name="imageData">Space reserved for the fax encoded bitmap. An exception will be thrown if this buffer is too small.</param>
    /// <param name="imageBits">The bitmap to be encoded.</param>
    /// <param name="bytesFileOffset">Offset of image data in bitmap file.</param>
    /// <param name="width">The width of the image.</param>
    /// <param name="height">The height of the image.</param>
    /// <returns>The size of the fax encoded image (0 on failure).</returns>
    internal static int DoFaxEncodingGroup4
        (
            ref byte[] imageData,
            byte[] imageBits,
            uint bytesFileOffset,
            uint width,
            uint height
        )
    {
        try
        {
            var bytesPerLineBmp = (width + 31) / 32 * 4;
            var writer = new BitWriter (ref imageData);
            for (uint y = 0; y < height; ++y)
            {
                FaxEncode2DRow (writer, bytesFileOffset, imageBits, y, y != 0 ? y - 1 : 0xffffffff, width, height,
                    bytesPerLineBmp);
            }

            writer.FlushBuffer();
            return writer.BytesWritten();
        }
        catch (Exception ex)
        {
            ex.GetType().NotUsed();
            return 0;
        }
    }

    /// <summary>
    /// Writes the image data.
    /// </summary>
    /// <param name="writer">The writer.</param>
    /// <param name="count">The count of bits (pels) to encode.</param>
    /// <param name="white">The color of the pels.</param>
    private static void WriteSample (BitWriter writer, uint count, bool white)
    {
        var terminatingCodes = white ? WhiteTerminatingCodes : BlackTerminatingCodes;
        var makeUpCodes = white ? WhiteMakeUpCodes : BlackMakeUpCodes;

        // The make-up code for 2560 will be written as often as required:
        while (count >= 2624)
        {
            writer.WriteTableLine (makeUpCodes, 39); // Magic: 2560
            count -= 2560;
        }

        // A make-up code for a multiple of 64 will be written if required:
        if (count > 63)
        {
            var line = count / 64 - 1;
            writer.WriteTableLine (makeUpCodes, line);
            count -= (line + 1) * 64;
        }

        // And finally the terminating code for the remaining value (0 through 63):
        writer.WriteTableLine (terminatingCodes, count);
    }
}

/// <summary>
/// The BitReader class is a helper to read bits from an in-memory bitmap file.
/// </summary>
class BitReader
{
    readonly byte[] _imageBits;
    uint _bytesOffsetRead;
    readonly uint _bytesFileOffset;
    byte _buffer;
    uint _bitsInBuffer;
    readonly uint _bitsTotal; // Bits we may read (bits per image line)

    /// <summary>
    /// Initializes a new instance of the <see cref="BitReader"/> class.
    /// </summary>
    /// <param name="imageBits">The in-memory bitmap file.</param>
    /// <param name="bytesFileOffset">The offset of the line to read.</param>
    /// <param name="bits">The count of bits that may be read (i. e. the width of the image for normal usage).</param>
    internal BitReader (byte[] imageBits, uint bytesFileOffset, uint bits)
    {
        _imageBits = imageBits;
        _bytesFileOffset = bytesFileOffset;
        _bitsTotal = bits;
        _bytesOffsetRead = bytesFileOffset;
        _buffer = imageBits[_bytesOffsetRead];
        _bitsInBuffer = 8;
    }

    /// <summary>
    /// Sets the position within the line (needed for 2D encoding).
    /// </summary>
    /// <param name="position">The new position.</param>
    internal void SetPosition (uint position)
    {
        _bytesOffsetRead = _bytesFileOffset + (position >> 3);
        _buffer = _imageBits[_bytesOffsetRead];
        _bitsInBuffer = 8 - (position & 0x07);
    }

    /// <summary>
    /// Gets a single bit at the specified position.
    /// </summary>
    /// <param name="position">The position.</param>
    /// <returns>True if bit is set.</returns>
    internal bool GetBit (uint position)
    {
        if (position >= _bitsTotal)
            return false;
        SetPosition (position);
        uint dummy;
        return (PeekByte (out dummy) & 0x80) > 0;
    }

    /// <summary>
    /// Returns the bits that are in the buffer (without changing the position).
    /// Data is MSB aligned.
    /// </summary>
    /// <param name="bits">The count of bits that were returned (1 through 8).</param>
    /// <returns>The MSB aligned bits from the buffer.</returns>
    internal byte PeekByte (out uint bits)
    {
        // TODO: try to make this faster!
        if (_bitsInBuffer == 8)
        {
            bits = 8;
            return _buffer;
        }

        bits = _bitsInBuffer;
        return (byte)(_buffer << (int)(8 - _bitsInBuffer));
    }

    /// <summary>
    /// Moves the buffer to the next byte.
    /// </summary>
    internal void NextByte()
    {
        _buffer = _imageBits[++_bytesOffsetRead];
        _bitsInBuffer = 8;
    }

    /// <summary>
    /// "Removes" (eats) bits from the buffer.
    /// </summary>
    /// <param name="bits">The count of bits that were processed.</param>
    internal void SkipBits (uint bits)
    {
        Debug.Assert (bits <= _bitsInBuffer, "Buffer underflow");
        if (bits == _bitsInBuffer)
        {
            NextByte();
            return;
        }

        _bitsInBuffer -= bits;
    }
}

/// <summary>
/// A helper class for writing groups of bits into an array of bytes.
/// </summary>
class BitWriter
{
    /// <summary>
    /// Initializes a new instance of the <see cref="BitWriter"/> class.
    /// </summary>
    /// <param name="imageData">The byte array to be written to.</param>
    internal BitWriter (ref byte[] imageData)
    {
        _imageData = imageData;
    }

    /// <summary>
    /// Writes the buffered bits into the byte array.
    /// </summary>
    internal void FlushBuffer()
    {
        if (_bitsInBuffer > 0)
        {
            var bits = 8 - _bitsInBuffer;
            WriteBits (0, bits);
        }
    }

    /// <summary>
    /// Masks for n bits in a byte (with n = 0 through 8).
    /// </summary>
    static readonly uint[] masks = { 0, 1, 3, 7, 15, 31, 63, 127, 255 };

    /// <summary>
    /// Writes bits to the byte array.
    /// </summary>
    /// <param name="value">The bits to be written (LSB aligned).</param>
    /// <param name="bits">The count of bits.</param>
    internal void WriteBits (uint value, uint bits)
    {
#if true

        // TODO: Try to make this faster!

        // If we have to write more bits than fit into the buffer, we fill
        // the buffer and call the same routine recursively for the rest.
#if USE_GOTO

        // Use GOTO instead of end recursion: (is this faster?)
        SimulateRecursion:
#endif
        if (bits + _bitsInBuffer > 8)
        {
            // We can't add all bits this time.
            var bitsNow = 8 - _bitsInBuffer;
            var bitsRemainder = bits - bitsNow;
            WriteBits (value >> (int)bitsRemainder, bitsNow); // that fits
#if USE_GOTO
            bits = bitsRemainder;
            goto SimulateRecursion;
#else
        WriteBits(value, bitsRemainder);
        return;
#endif
        }

        _buffer = (_buffer << (int)bits) + (value & masks[bits]);
        _bitsInBuffer += bits;

        if (_bitsInBuffer == 8)
        {
            // The line below will sometimes throw a System.IndexOutOfRangeException while PDFsharp tries different formats for monochrome bitmaps (exception occurs if CCITT encoding requires more space than an uncompressed bitmap).
            _imageData[_bytesOffsetWrite] = (byte)_buffer;
            _bitsInBuffer = 0;
            ++_bytesOffsetWrite;
        }
#else
            // Simple implementation writing bit by bit:
            int mask = 1 << (int)(bits - 1);
            for (int b = 0; b < bits; ++b)
            {
                if ((value & mask) != 0)
                    buffer = (buffer << 1) + 1;
                else
                    buffer = buffer << 1;
                ++bitsInBuffer;
                mask /= 2;
                if (bitsInBuffer == 8)
                {
                    imageData[bytesOffsetWrite] = (byte)buffer;
                    bitsInBuffer = 0;
                    ++bytesOffsetWrite;
                }
            }
#endif
    }

    /// <summary>
    /// Writes a line from a look-up table.
    /// A "line" in the table are two integers, one containing the values, one containing the bit count.
    /// </summary>
    internal void WriteTableLine (uint[] table, uint line)
    {
        var value = table[line * 2];
        var bits = table[line * 2 + 1];
        WriteBits (value, bits);
    }

    [Obsolete]
    internal void WriteEOL()
    {
        // Not needed for PDF.
        WriteTableLine (PdfImage.WhiteMakeUpCodes, 40);
    }

    /// <summary>
    /// Flushes the buffer and returns the count of bytes written to the array.
    /// </summary>
    internal int BytesWritten()
    {
        FlushBuffer();
        return _bytesOffsetWrite;
    }

    private int _bytesOffsetWrite;
    private readonly byte[] _imageData;
    private uint _buffer;
    private uint _bitsInBuffer;
}
